/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.chaining;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.R;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.data.Region;
import com.onegravity.sudoku.solver.util.BitSetFactory;
import com.onegravity.sudoku.solver.util.HtmlLoader;
import com.onegravity.sudoku.solver.util.Link;

/**
 * Region Forcing Chain hint.
 */
public class RegionForcingChainHint extends ChainHint {

	private static final long serialVersionUID = -3844332451922396436L;
	
	private final Region region;
    private final int value;
    private Map<Integer, Potential> chains;

    public RegionForcingChainHint(ChainMetaInfo metaInfo, Map<Cell, BitSet> removablePotentials, Region region, int value, Map<Integer, Potential> chains) {
        super(metaInfo, removablePotentials, true, true);
        this.region = region;
        this.value = value;
        this.chains = chains;
    }

    @Override
    public boolean isWorth() {
        return mMetaInfo.mConfig.useRegionForcingChains() && super.isWorth();
    }

    @Override
    public int getFlatViewCount() {
        return chains.size();
    }

    @Override
    public Region[] getRegions(HintType hintType) {
        return (hintType!=HintType.SMALL_CLUE) ? new Region[] {region} : null;
    }
    
    Region getRegion() {
        return this.region;
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType type) {
        Cell dstCell = chains.values().iterator().next().cell;
    	if (type==HintType.SMALL_CLUE) return Collections.singletonList(dstCell);
        return Arrays.asList(new Cell[] {dstCell});
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType type, int viewNum) {
        Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
    	if (type==HintType.SMALL_CLUE) {
            for (Potential curTarget : chains.values()) {
                Potential curSource = getSrcPotential(curTarget);
    			result.put(curSource.cell, BitSetFactory.createOneElement(curSource.value));
            }
    	}
		return result;
    }
    
    @Override
    public Map<Cell, BitSet> getAddedPotentials(HintType type, int viewNum) {
        Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        Potential targetPotential = getTargetPotential(viewNum);
    	switch (type) {
    	case BIG_CLUE:
            for (Potential curTarget : chains.values()) {
    			if (curTarget.isOn) result.put(curTarget.cell, BitSetFactory.createOneElement(curTarget.value));
                Potential curSource = getSrcPotential(curTarget);
                if (curSource.isOn) result.put(curSource.cell, BitSetFactory.createOneElement(curSource.value));
            }
    		break;
    	case HINT: {
            result = (viewNum >= getFlatViewCount()) ?
            		 super.getNestedAddedPotentials(type, viewNum) :
            		 getColorPotentials(targetPotential, true, false);
    	}
		case SMALL_CLUE: break;
    	}
        return result;
    }
    
    @Override
    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType type, int viewNum) {
    	Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        Potential targetPotential = getTargetPotential(viewNum);
        switch (type) {
    	case BIG_CLUE:
            for (Potential curTarget : chains.values()) {
    			if (! curTarget.isOn) result.put(curTarget.cell, BitSetFactory.createOneElement(curTarget.value));
                Potential curSource = getSrcPotential(curTarget);
                if (! curSource.isOn) result.put(curSource.cell, BitSetFactory.createOneElement(curSource.value));
            }
    		break;
    	case HINT: {
            result = (viewNum >= getFlatViewCount()) ?
            		 super.getNestedSecondarySelectedPotentials(type, viewNum) :
            		 getColorPotentials(targetPotential, false, false);
    	}
		case SMALL_CLUE: break;
    	}
    	return result;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType type) {
        return (type==HintType.HINT) ? super.getRemovedPotentials(type) : null;
    }

    @Override
    public Collection<Link> getLinks(HintType type, int viewNum) {
        if (viewNum >= getFlatViewCount()) return super.getNestedLinks(type, viewNum);
        Potential target = getTargetPotential(viewNum);
        return (type==HintType.HINT) ? super.getLinks(target) : null;
    }

    private Potential getTargetPotential(int viewNum) {
        int value = getValue(viewNum);
        Potential target = chains.get(value);
        return target;
    }

    private int getValue(int index) {
        Iterator<Integer> iter = chains.keySet().iterator();
        while (index > 0) {
            iter.next();
            index--;
        }
        return iter.next();
    }

    @Override
    public int getFlatComplexity() {
        int result = 0;
        for (Potential target : chains.values())
            result += super.getAncestorCount(target);
        return result;
    }

    @Override
    protected Collection<Potential> getChainsTargets() {
        return Collections.unmodifiableCollection(this.chains.values());
    }

    @Override
    protected Potential getChainTarget(int viewNum) {
        return getTargetPotential(viewNum);
    }

    @Override
    public int getChainingSortKey() {
        return 6;
    }

    public double getDifficulty() {
        return mMetaInfo.getDifficulty() + getLengthDifficulty();
    }

    @Override
    public String getName() {
        String name = mMetaInfo.getCommonName(this);
        if (name != null) return name;
        name = (mMetaInfo.isDynamic()) ? HintResource.DYNAMIC_REGION_FORCING_CHAIN.getName() : HintResource.REGION_FORCING_CHAIN.getName();
        return name + super.getNameSuffix();
    }

    @Override
    public Uri getLink() {
        return mMetaInfo.isDynamic() ? HintResource.DYNAMIC_REGION_FORCING_CHAIN.getUrl() : HintResource.REGION_FORCING_CHAIN.getUrl();
    }

    @Override
    protected Potential getResult() {
        return chains.values().iterator().next();
    }

	@Override
	public String getHintHtml(HintType type) {
    	HintResource hintRes = (mMetaInfo.isDynamic()) ? HintResource.DYNAMIC_REGION_FORCING_CHAIN : HintResource.REGION_FORCING_CHAIN;
    	String result = HtmlLoader.loadHtml(hintRes.hint(type));
        StringBuffer assertions = new StringBuffer();
        ArrayList<Cell> cells = new ArrayList<Cell>();
    	String theIf = HintResource.getString(R.string.if_);
    	String then = HintResource.getString(R.string.then);
        for (Potential curTarget : chains.values()) {
            Potential curSource = getSrcPotential(curTarget);
            assertions.append("<li>" + theIf + " " + curSource.toWeakString() + ", " + then + " " + curTarget.toStrongString());
            cells.add(curSource.cell);
        }
        String valueName = Integer.toString(value);
        String regionName = region.toFullString();
        Potential target = chains.values().iterator().next();
        String resultName = target.toStrongString();
        StringBuilder htmlChains = getChainsDetails();
        result = HtmlLoader.format(result, assertions, valueName, regionName, resultName, htmlChains, Cell.toString(cells.toArray(new Cell[cells.size()])), target.cell.toString(), target.value);
        return super.appendNestedChainsDetails(result);
	}
    
    @Override
    public String toString() {
        String prefix = mMetaInfo.getCommonName(this);
        if (prefix == null) {
        	HintResource hintRes = (mMetaInfo.isDynamic()) ? HintResource.DYNAMIC_REGION_FORCING_CHAIN : HintResource.REGION_FORCING_CHAIN;
        	prefix = hintRes.toString();
        }
        Potential dstPotential = chains.values().iterator().next();
        return prefix + ": " + value + " in " + region.toString() + " ==> " + dstPotential.toString() + (dstPotential.isOn ? " on" : " off");
    }

    private StringBuilder getChainsDetails() {
        StringBuilder htmlChains = new StringBuilder();
        int index = 1;
        for (Potential curTarget : chains.values()) {
            htmlChains.append(HintResource.getString(R.string.reasoning_chainDetails, index) + "</b>");
            String curChain = getHtmlChain(curTarget);
            htmlChains.append(curChain);
            index++;
        }
        return htmlChains;
    }

}